namespace AgileDashboard.AppCode
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Web;
    using System.Web.Caching;
    using System.Xml.XPath;

    using Microsoft.Security.Application;

    /// <summary>
    /// DAO for fetching and caching Project objects
    /// </summary>
    public class ProjectDao : DaoBase
    {
        private const string NoXmlInResponseFromServer = "No xml in response from server while trying to load project {0}";

        public ProjectDao(IDataConnection dataConnection) : base(dataConnection)
        {
        }

        public Project GetProjectByName(string projectName)
        {
            if (projectName == null)
            {
                throw new ArgumentNullException("projectName");
            }

            var proj = new Project();

            var query = string.Format("Scope?sel={1}&Where=Scope.Name='{0}'", Encoder.UrlEncode(projectName), Encoder.UrlEncode(string.Join(",", Project.VersionOneAttributeNameDictionary.Values)));

            var ret = DataConnection.Execute(query);

            if (ret != null && ret.Document != null)
            {
                var assetElem = ret.XPathSelectElement("/Assets/Asset");
                proj.ToDoStoryPoints = Util.GetValue<double>(Project.VersionOneAttributeNameDictionary["ToDoStoryPoints"], assetElem);
                proj.DoneStoryPoints = Util.GetValue<double>(Project.VersionOneAttributeNameDictionary["DoneStoryPoints"], assetElem);
                proj.TotalStoryPoints = Util.GetValue<double>(Project.VersionOneAttributeNameDictionary["TotalStoryPoints"], assetElem);
                proj.DisplayInAgileDashboard = Util.GetValue<bool>(Project.VersionOneAttributeNameDictionary["DisplayInAgileDashboard"], assetElem);
                proj.Name = Util.GetValue<string>(Project.VersionOneAttributeNameDictionary["Name"], assetElem);
            }
            else
            {
                var msg = string.Format(NoXmlInResponseFromServer, projectName);
                Log.Error(msg);
                throw new ApplicationException(msg);
            }
            return proj;
        }

        /// <summary>
        /// Returns a cached list of projects that are configured to display on the AgileDashboard in Version One and caches the results
        /// </summary>
        /// <returns>A collection of Project objects</returns>
        public ICollection<Project> GetAgileDashboardConfiguredProjects()
        {
            return GetAgileDashboardConfiguredProjects(false);
        }


        /// <summary>
        /// Returns the projects that are configured to display on the AgileDashboard in Version One and caches the results
        /// </summary>
        /// <param name="bypassCache">Ignore the cached version and requery Version One</param>
        /// <returns>A collection of Project objects</returns>
        public ICollection<Project> GetAgileDashboardConfiguredProjects(bool bypassCache)
        {
            const string CacheKey = "AgileDashboardConfiguredProjects";

            var configuredProjects = HttpRuntime.Cache[CacheKey] as ICollection<Project>;

            if (bypassCache || (configuredProjects == null))
            {
                Debug.WriteLine(string.Format("Cache miss for key {0}", CacheKey));
                if (Log.IsDebugEnabled)
                {
                    Log.DebugFormat("Cache miss for key {0}", CacheKey);
                }

                // get the configured projects from version one
                configuredProjects = this.LoadAgileDashboardConfiguredProjects();

                HttpRuntime.Cache.Insert(CacheKey, configuredProjects, null, DateTime.Now.AddMinutes(Properties.Settings.Default.ConfiguredProjectsCacheMinutes), Cache.NoSlidingExpiration, this.GetAgileDashboardProjectsCacheUpdateCallback);
            }
            else
            {
                Debug.WriteLine(string.Format("Cache hit for key {0}", CacheKey));
                if (Log.IsDebugEnabled)
                {
                    Log.DebugFormat("Cache hit for key {0}", CacheKey);
                }
            }

            return configuredProjects;
        }

        private ICollection<Project> LoadAgileDashboardConfiguredProjects()
        {
            var query = string.Format("Scope?sel={0}&Where={1}='True';AssetState!='Closed'", Encoder.UrlEncode(string.Join(",", Project.VersionOneAttributeNameDictionary.Values)), Project.VersionOneAttributeNameDictionary["DisplayInAgileDashboard"]);

            var ret = DataConnection.Execute(query);
            var assets = ret.XPathSelectElement("Assets");
            if (assets != null)
            {
                var linqQuery = from c in assets.Elements("Asset")
                                select 
                                    new Project
                                        {
                                            ToDoStoryPoints = Util.GetValue<double>(Project.VersionOneAttributeNameDictionary["ToDoStoryPoints"], c),
                                            DoneStoryPoints = Util.GetValue<double>(Project.VersionOneAttributeNameDictionary["DoneStoryPoints"], c),
                                            TotalStoryPoints = Util.GetValue<double>(Project.VersionOneAttributeNameDictionary["TotalStoryPoints"], c),
                                            DisplayInAgileDashboard = Util.GetValue<bool>(Project.VersionOneAttributeNameDictionary["DisplayInAgileDashboard"], c),
                                            Name = Util.GetValue<string>(Project.VersionOneAttributeNameDictionary["Name"], c)
                                        };

                return linqQuery.ToList();
            }

            return null;
        }

        // caching callback method
        private void GetAgileDashboardProjectsCacheUpdateCallback(string key, CacheItemUpdateReason reason, out object expensiveObject, out CacheDependency dependency, out DateTime absoluteExpiration, out TimeSpan slidingExpiration)
        {
            absoluteExpiration = Cache.NoAbsoluteExpiration;
            slidingExpiration = Cache.NoSlidingExpiration;
            dependency = null;
            expensiveObject = null;

            var configuredProjects = HttpRuntime.Cache[key] as ICollection<Project>;

            if (configuredProjects == null)
            {
                // for some reason, there isn't a valid object in the cache. Not good.
                // If this is the case, the cache manager will remove the cached object when this callback returns
                return;
            }

            // if there is a valid cached object, replace it with a fresh object
            configuredProjects = this.LoadAgileDashboardConfiguredProjects();

            expensiveObject = configuredProjects;
            absoluteExpiration = DateTime.Now.AddMinutes(Properties.Settings.Default.ConfiguredProjectsCacheMinutes);
            Debug.WriteLine(string.Format("Refreshed cache for key {0}", key));
            if (Log.IsDebugEnabled)
            {
                Log.DebugFormat("Refreshed cache for key {0}", key);
            }
        }
    }
}